/*
 * ________________________________________________________________________________________________________
 * Copyright (c) 2018-2019 InvenSense Inc. All rights reserved.
 *
 * This software, related documentation and any modifications thereto (collectively "Software") is subject
 * to InvenSense and its licensors' intellectual property rights under U.S. and international copyright
 * and other intellectual property rights laws.
 *
 * InvenSense and its licensors retain all intellectual property and proprietary rights in and to the Software
 * and any use, reproduction, disclosure or distribution of the Software without an express license agreement
 * from InvenSense is strictly prohibited.
 *
 * EXCEPT AS OTHERWISE PROVIDED IN A LICENSE AGREEMENT BETWEEN THE PARTIES, THE SOFTWARE IS
 * PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
 * TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
 * EXCEPT AS OTHERWISE PROVIDED IN A LICENSE AGREEMENT BETWEEN THE PARTIES, IN NO EVENT SHALL
 * INVENSENSE BE LIABLE FOR ANY DIRECT, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, OR ANY
 * DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
 * OF THE SOFTWARE.
 * ________________________________________________________________________________________________________
 */


#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/delay.h>
#include "icm406xx_driver.h"

/*! @defgroup icm406xx_spi_src
 *  @brief icm406xx spi driver module
 @{*/
/*! the maximum of transfer buffer size */
#define ICM_MAX_BUFFER_SIZE      32

static struct spi_device *icm_spi_client;

/*!
 * @brief define spi wirte function
 *
 * @param dev_addr sensor device address
 * @param reg_addr register address
 * @param data the pointer of data buffer
 * @param len block size need to write
 *
 * @return zero success, non-zero failed
 * @retval zero success
 * @retval non-zero failed
*/
static char icm_spi_write_block(u8 dev_addr, u8 reg_addr, u8 *data, u8 len)
{
	struct spi_device *client = icm_spi_client;
	u8 buffer[ICM_MAX_BUFFER_SIZE + 1];
	struct spi_transfer xfer = {
		.tx_buf     = buffer,
		.len        = len + 1,
	};
	struct spi_message msg;

	if (len > ICM_MAX_BUFFER_SIZE)
		return -EINVAL;

	buffer[0] = reg_addr;
	memcpy(&buffer[1], data, len);

	spi_message_init(&msg);
	spi_message_add_tail(&xfer, &msg);
	return spi_sync(client, &msg);
}

/*!
 * @brief define spi read function
 *
 * @param dev_addr sensor device address
 * @param reg_addr register address
 * @param data the pointer of data buffer
 * @param len block size need to read
 *
 * @return zero success, non-zero failed
 * @retval zero success
 * @retval non-zero failed
*/
static char icm_spi_read_block(u8 dev_addr, u8 reg_addr, u8 *data, u8 len)
{
	struct spi_device *client = icm_spi_client;
	u8 reg = reg_addr | 0x80;/* read: MSB = 1 */
	struct spi_transfer xfer[2] = {
		[0] = {
			.tx_buf = &reg,
			.len = 1,
		},
		[1] = {
			.rx_buf = data,
			.len = len,
		}
	};
	struct spi_message msg;

	spi_message_init(&msg);
	spi_message_add_tail(&xfer[0], &msg);
	spi_message_add_tail(&xfer[1], &msg);
	return spi_sync(client, &msg);
}

s8 icm_burst_read_wrapper(u8 dev_addr, u8 reg_addr, u8 *data, u16 len)
{
	struct spi_device *client = icm_spi_client;
	u8 reg = reg_addr | 0x80;/* read: MSB = 1 */
	struct spi_transfer xfer[2] = {
		[0] = {
			.tx_buf = &reg,
			.len = 1,
		},
		[1] = {
			.rx_buf = data,
			.len = len,
		}
	};
	struct spi_message msg;

	spi_message_init(&msg);
	spi_message_add_tail(&xfer[0], &msg);
	spi_message_add_tail(&xfer[1], &msg);
	return spi_sync(client, &msg);
}
EXPORT_SYMBOL(icm_burst_read_wrapper);
/*!
 * @brief BMI probe function via spi bus
 *
 * @param client the pointer of spi client
 *
 * @return zero success, non-zero failed
 * @retval zero success
 * @retval non-zero failed
*/
static int icm_spi_probe(struct spi_device *client)
{
	int status;
	int err = 0;
	struct icm_client_data *client_data = NULL;

	if (NULL == icm_spi_client)
		icm_spi_client = client;
	else{
		dev_err(&client->dev, "This driver does not support multiple clients!\n");
		return -EBUSY;
	}

	client->bits_per_word = 8;
	status = spi_setup(client);
	if (status < 0) {
		dev_err(&client->dev, "spi_setup failed!\n");
		return status;
	}

	client_data = kzalloc(sizeof(struct icm_client_data), GFP_KERNEL);
	if (NULL == client_data) {
		dev_err(&client->dev, "no memory available");
		err = -ENOMEM;
		goto exit_err_clean;
	}

	client_data->device.bus_read = icm_spi_read_block;
	client_data->device.bus_write = icm_spi_write_block;

	return icm_probe(client_data, &client->dev);

exit_err_clean:
	if (err)
		icm_spi_client = NULL;
	return err;
}

/*!
 * @brief shutdown icm device in spi driver
 *
 * @param client the pointer of spi client
 *
 * @return no return value
*/
static void icm_spi_shutdown(struct spi_device *client)
{
#ifdef CONFIG_PM
	icm_suspend(&client->dev);
#endif
}

/*!
 * @brief remove icm spi client
 *
 * @param client the pointer of spi client
 *
 * @return zero
 * @retval zero
*/
static int icm_spi_remove(struct spi_device *client)
{
	int err = 0;
	err = icm_remove(&client->dev);
	icm_spi_client = NULL;

	return err;
}

#ifdef CONFIG_PM
/*!
 * @brief suspend icm device in spi driver
 *
 * @param dev the pointer of device
 *
 * @return zero
 * @retval zero
*/
static int icm_spi_suspend(struct device *dev)
{
	int err = 0;
	err = icm_suspend(dev);
	return err;
}

/*!
 * @brief resume icm device in spi driver
 *
 * @param dev the pointer of device
 *
 * @return zero
 * @retval zero
*/
static int icm_spi_resume(struct device *dev)
{
	int err = 0;
	/* post resume operation */
	err = icm_resume(dev);

	return err;
}

/*!
 * @brief register spi device power manager hooks
*/
static const struct dev_pm_ops icm_spi_pm_ops = {
	/**< device suspend */
	.suspend = icm_spi_suspend,
	/**< device resume */
	.resume  = icm_spi_resume
};
#endif

/*!
 * @brief register spi device id
*/
static const struct spi_device_id icm_id[] = {
	{ SENSOR_NAME, 0 },
	{ }
};
MODULE_DEVICE_TABLE(spi, icm_id);

/*!
 * @brief register spi driver hooks
*/
static struct spi_driver icm_spi_driver = {
	.driver = {
		.owner = THIS_MODULE,
		.name  = SENSOR_NAME,
#ifdef CONFIG_PM
		.pm = &icm_spi_pm_ops,
#endif
	},
	.id_table = icm_id,
	.probe    = icm_spi_probe,
	.shutdown = icm_spi_shutdown,
	.remove   = icm_spi_remove
};

/*!
 * @brief initialize icm spi module
 *
 * @return zero success, non-zero failed
 * @retval zero success
 * @retval non-zero failed
*/
static int __init icm_spi_init(void)
{
	return spi_register_driver(&icm_spi_driver);
}

/*!
 * @brief remove icm spi module
 *
 * @return no return value
*/
static void __exit icm_spi_exit(void)
{
	spi_unregister_driver(&icm_spi_driver);
}


MODULE_DESCRIPTION("ICM406XX SPI DRIVER");
MODULE_LICENSE("GPL v2");

module_init(icm_spi_init);
module_exit(icm_spi_exit);
/*@}*/

